JavaScript 運算子大概可以分為幾大類:算數運算子、邏輯運算子、賦值運算子、比較運算子和位元運算子...
大部分的運算子在閱讀規範後都能快速上手,但其中的位元運算子在理解上相對沒那麼直覺,但 只要掌握了二進制表達方式,這就只是塊小小小雞蛋糕了
所以 先從了解 二進制binary 開始吧!
在數字系統裡,以進位制表達的方式有 2、8、10、16 進位等,而一般日常熟悉的數字都是十進制,也就是以 10 作為進位基數的表達方式。
二進制 就是以 2 為底數的記數方式,以1
和0
做數字表示,目前計算機就是以二進制的方式來儲存資料,1
代表open 0
代表 off, 而每個數字就稱為一個 位元bit。
下圖是二進制與十進制數字的表示比較
這次要介紹 6 種位元運算子 bitwise operator :
& Bitwise AND
| Bitwise OR
~ Bitwise NOT
^ Bitwise XOR
<< Left Shift
>> Sign-Propagating Right Shift
&
位元運算子是不是很眼熟? 這個符號 &
在邏輯運算子中也有!
邏輯運算子中的 And &&
代表兩個條件都符合才是 true
, 其一不符合就是 false
true && true === true
false && false === false
false && true === false
而位元運算子的 &
也是同樣邏輯, 先將數字轉換為二進制後,把握 0
代表 false, 1
代表 true 的原則,再來就是一樣的邏輯判斷。
所以當 1 和 0 比對,等同比較 true && false
,結果是 false 0
那 1 和 1 呢? true && true
比對的結果當然就是 true 1
小試身手 >> 3 & 2
結果是多少?
//先將 3 和 2 轉為二進制表達
//每個位元做 1 , 0 比較
011 -> 3
& 010 -> 2
--------
010 -> 2
第一位: 1 & 0 === 0
第二位: 1 & 1 === 1
第三位: 0 & 0 === 0
再來一題 >> 5 & 7
?
101 -> 5
& 111 -> 7
--------
101 -> 5
由上面的舉例發現, 在二進制表示法中,奇數的第一位數都是 1
,偶數的第一位都是 0
,利用此特性就可以輕鬆篩選出奇數偶數
//重點比對第一位, 所以配上 & 1 判斷第一位是否為 1 或 0
10 & 1 ->0000
9 & 1 -> 0001
8 & 1 -> 0000
7 & 1 -> 001
6 & 1 -> 000
5 & 1 -> 001
4 & 1 -> 000
3 & 1 -> 001
2 & 1 -> 000
1 & 1 -> 001
//奇數 & 1 結果都是 1
//偶數 & 1 結果都是 0
//實作判斷奇數偶數 function
function isOdd(number){
return (number & 1)===1;
}
function isEven(number){
return (number & 1)===0;
}
isOdd(3) === true
isEven(6) === true
|
位元運算子|
運算子只是將比對規則改為 OR 判斷,有 1
就 return 1
否則 return 0
1 true || 1 true === 1 true
0 false || 0 false === 0 false
0 false || 1 true === 1 true
剛剛 3 & 2 === 2
改為 3 | 2
?
011 -> 3
| 010 -> 2
------
011 -> 3
第一位: 1 | 0 === 1
第二位: 1 | 1 === 1
第三位: 0 | 0 === 0
&
運算子: 1 & 1
return 1
, 而0 & 1
return 0
|
運算子: 1 | 1
return 1
, 而0 | 1
return 1
^
位元運算子^
XOR 運算子,全名 Exclusive Or,與一般的邏輯不同,^
是當數字兩兩相同是 false 0
,不同則是true 1
一樣用 3 ^ 2
試看看吧
011 -> 3
^ 010 -> 2
------
001 -> 1
第一位: 1 ^ 0 === 1
第二位: 1 ^ 1 === 0
第三位: 0 ^ 0 === 0
<<
Left Shift Operator 和 >>
Right Shift Operator這兩個運算子用來移動數字往符號指向的方向,<<
代表位元向左移, 空出來的位元補上0
,>>
代表位元向右移。
~
位元運算子~
NOT 運算子使用的是 補數 implement 的概念,數字被轉換為 32 位元的二進制表達,將 0
轉為1
, 1
轉為 0
看到這個名詞時 google 看了很多文章,wiki 的解釋點了不下 10 次
正數和 0 的二補數就是該數字本身,負數的二補數則是將其對應正數按位元取反再加 1 - wikipedia
一直無法透徹理解為什麼要這樣設計,直到我看到這個 youtube 影片
Twos complement: Negative numbers in binary 推推! 原來是這樣啊
實際上電腦會分配一個符號位 sign bit,來作為正負號表示, 0
表示正數 1
表示負數
第一種最直覺的負數表示為 原碼 sign-and-magnitude
但使用這種表達方式會發現一個問題
電腦進行 -5 + 5
的運算時,結果會是 1101 + 0101 = 10010 等於 2
?
安堆~~~~
//原碼 sign-and-magnitude
sign
bit 4 2 1
--------------
1 1 1 1 // -7
1 1 1 0 // -6
1 1 0 1 // -5
1 1 0 0 // -4
1 0 1 1 // -3
1 0 1 0 // -2
1 0 0 1 // -1
1 0 0 0 // -0
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
看來上面那種負數表示方法不太適用於電腦運算
那有另一種表示方法稱為 一補數(ones' complement)
在一補數表示時要做位元轉換,負數是將 1
0
互換,正數不變動。
所以 -5
是從 0101
位元轉換為 1010
表示
那這種方法有解決加總的結果嗎?
再試試 -5 + 5
, 1010 + 0101 = 10000 等於 -0
恩 看來好一點,但這個負號不覺得怪怪的嗎 -0
?
再另外試試 5 + (-3)
0101 + 1100 = 100001 因為只取4位數先不看最左邊的 1,加總起來的數字變為 1
不行啊 數字還是不對
//一補數 ones' complement
sign
bit 4 2 1
--------------
1 0 0 0 // -7
1 0 0 1 // -6
1 0 1 0 // -5
1 0 1 1 // -4
1 1 0 0 // -3
1 1 0 1 // -2
1 1 1 0 // -1
1 0 0 0 // -0
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
在一補數的正負數加總中可以發現和正確的答案都只差 1
所以 就產生了 一補數加上 1 的方式 二補數(two's complement)
再試一次剛剛的加總 -5 + 5
1011 + 0101 = 10000 一樣不看最左邊的數字,很好 等於0
那 5 + (-3)
呢?
0101 + 1101 = 10010
耶 是我們要的 2
//二補數 two's complement
sign
bit 4 2 1
--------------
1 0 0 1 // -7
1 0 1 0 // -6
1 0 1 1 // -5
1 1 0 0 // -4
1 1 0 1 // -3
1 1 1 0 // -2
1 1 1 1 // -1
0 0 0 0 // 0
0 0 0 1 // 1
0 0 1 0 // 2
0 0 1 1 // 3
0 1 0 0 // 4
0 1 0 1 // 5
0 1 1 0 // 6
0 1 1 1 // 7
其實二補數的負數算法也很簡單,只要將最左邊的數字帶上負號做加總就跟 原碼 sign-and-magnitude 算法一樣
1001 -> -8+1 = -7
1010 -> -8+2 = -6
二補數很好的解決了加總的問題,也不存在奇怪的 -0
現在再看看 wiki 說的
正數和 0 的二補數就是該數字本身。負數的二補數則是將其對應正數按位元取反再加 1
是不是就理解了呢?
看完上面了解補數的運作後,~
NOT 就是對每個位元進行補數轉換
sign
bit 4 2 1 ~ NOT
---------------------------------------
1 0 0 1 // -7 | ~-7 0110 // 6
1 0 1 0 // -6 | ~-6 0101 // 5
1 0 1 1 // -5 | ~-5 0100 // 4
1 1 0 0 // -4 | ~-4 0011 // 3
1 1 0 1 // -3 | ~-3 0010 // 2
1 1 1 0 // -2 | ~-2 0001 // 1
1 1 1 1 // -1 | ~-1 0000 // 0
0 0 0 0 // 0 | ~0 1111 // -1
0 0 0 1 // 1 | ~1 1110 // -2
0 0 1 0 // 2 | ~2 1101 // -3
0 0 1 1 // 3 | ~3 1100 // -4
0 1 0 0 // 4 | ~4 1011 // -5
0 1 0 1 // 5 | ~5 1010 // -6
0 1 1 0 // 6 | ~6 1001 // -7
0 1 1 1 // 7 | ~7 1000 // -8
如果要在腦中做補位轉來轉去太困難,其實也可以直接套用這個公式 -(x + 1)
,就可以知道 ~
下的數字是多少了!
做做下面幾題練練看吧
~16
~56
~63
sign
bit
64 32 16 8 4 2 1
------------------------------------
0 0 1 0 0 0 0 // 16
1 1 0 1 1 1 1 // ~16 -> -64+32+8+4+2+1 = -17
0 1 1 1 0 0 0 // 56
1 0 0 0 1 1 1 // ~56 -> -54+4+2+1 = -57
0 1 1 1 1 1 1 // 63
1 0 0 0 0 0 0 // ~63 -> -64
~~
這裡教個小技巧,你知道在JS中 ~~ 3.2
是什麼意思嗎
剛剛學到 ~
是補位,那兩個 ~~
是補位再補位,等於自己本身,那 這個有什麼意義嗎?
適用在對非整數數字無條件捨去! 因為 JavaScript 位元運算子中都是將數字轉為 32 位元的整數呈現,所以可以利用這個原則得到取整數這個效果
//對小數點 2.5 進行兩次 NOT 位元運算
~2.5 === -3
~(-3)=== 2
以上
一直很想好好整理的位元運算子完成了呢
其中補數觀念還不太理解的朋友真的推薦去看看這支影片 Twos complement: Negative numbers in binary
對了 在 Mac 的計算機也可以進行位元運算唷~~~
在計算機上按快捷鍵 cmd + 3
就能快速切換成程式設計計算機
來玩玩看吧!